﻿using System.Collections.Generic;
using System.Linq;
using CID.Domain.Audit;
using CID.Domain.Development;
using CID.Domain.Exceptions;
using CID.Tests.Support;
using NUnit.Framework;
using Rhino.Mocks;
using Environment = CID.Domain.Operations.Environment;

namespace CID.Domain.Tests.Operations
{
    [TestFixture]
    class EnvironmentTests : BaseFunctionalTests
    {
        private const string Name = "Environment Name";
        private const string Description = "Environment Description";
        private Environment environment;

        public override void SetUp()
        {
            base.SetUp();
            environment = new Environment(Name, Description, Users.StubsBuilder.BuildUserStub());
        }

        #region Constructor
        [Test]
        public void CreateNewEnvironment()
        {
            var user = Users.StubsBuilder.BuildUserStub();
            var environment = new Environment(Name, Description, user);

            Assert.AreEqual(Name, environment.Name);
            Assert.AreEqual(Description, environment.Description);
            Assert.AreEqual(user, environment.Responsible);
            Assert.IsTrue(environment.Enabled);
            Assert.IsFalse(environment.IsProduction);
            CollectionAssert.IsEmpty(environment.ActiveVersions);
            CollectionAssert.IsEmpty(environment.Assignments);
        }
        #endregion

        #region Assign
        [Test]
        public void Assign_NoPreviousAssignment_ShouldSucceed()
        {
            var server = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();

            environment.Assign(server, serverRole);

            var assignment = environment.Assignments.FirstOrDefault(x => (x.Server.Equals(server) && x.ServerRole.Equals(serverRole)));
            Assert.IsNotNull(assignment);
            Assert.AreEqual(1, environment.Assignments.Count());
            server.AssertWasCalled(x => x.AddAssignment(assignment));
            serverRole.AssertWasCalled(x => x.AddAssignment(assignment));
        }

        [Test, ExpectedException(typeof(DuplicateAssignmentException))]
        public void Assign_DuplicateAssignment_ShouldThrowException()
        {
            var server = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();
            var assignment = StubsBuilder.BuildServerAssignmentStub(environment, server, serverRole);
            environment.AddAssignment(assignment);

            environment.Assign(server, serverRole);
        }
        #endregion

        #region Unassign
        [Test]
        public void Unassign_AnotherPreviousAssignment_NothingShouldHappen()
        {
            var server = StubsBuilder.BuildServerStub();
            var anotherServer = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();
            environment.Assign(anotherServer, serverRole);
            var assignment = environment.Assignments.FirstOrDefault();

            environment.Unassign(server, serverRole);

            Assert.AreEqual(1, environment.Assignments.Count());
            anotherServer.AssertWasNotCalled(x => x.RemoveAssignment(assignment));
            serverRole.AssertWasNotCalled(x => x.RemoveAssignment(assignment));
        }

        [Test]
        public void Unassign_PreviousAssignment_ShouldSucceed()
        {
            var server = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();
            environment.Assign(server, serverRole);
            var assignment = environment.Assignments.FirstOrDefault();

            environment.Unassign(server, serverRole);

            Assert.AreEqual(0, environment.Assignments.Count());
            server.AssertWasCalled(x => x.RemoveAssignment(assignment));
            serverRole.AssertWasCalled(x => x.RemoveAssignment(assignment));
        }
        #endregion

        #region AddAssignment
        [Test]
        public void AddAssignment_NoPreviousAssignment_ShouldSucceed()
        {
            var server = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();
            var assignment = StubsBuilder.BuildServerAssignmentStub(environment, server, serverRole);

            environment.AddAssignment(assignment);

            var retrievedAssignment = environment.Assignments.FirstOrDefault(x => (x.Server.Equals(server) && x.ServerRole.Equals(serverRole)));
            Assert.IsNotNull(retrievedAssignment);
            Assert.AreEqual(1, environment.Assignments.Count());
        }

        [Test, ExpectedException(typeof(InvalidAssignmentException))]
        public void AddAssignment_AssignmentForAnotherEnvironment_ShouldThrowException()
        {
            var anotherEnvironment = StubsBuilder.BuildEnvironmentStub();
            var assignment = StubsBuilder.BuildServerAssignmentStub(anotherEnvironment);

            environment.AddAssignment(assignment);
        }

        [Test, ExpectedException(typeof(DuplicateAssignmentException))]
        public void AddAssignment_DuplicateAssignment_ShouldThrowException()
        {
            var server = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();
            var assignment = StubsBuilder.BuildServerAssignmentStub(environment, server, serverRole);
            var duplicateAssignment = StubsBuilder.BuildServerAssignmentStub(environment, server, serverRole);

            environment.AddAssignment(assignment);
            environment.AddAssignment(duplicateAssignment);
        }
        #endregion

        #region RemoveAssignment
        [Test]
        public void RemoveAssignment_NoPreviousAssignment_NothingShouldHappen()
        {
            var server = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();
            var assignment = StubsBuilder.BuildServerAssignmentStub(environment, server, serverRole);

            environment.RemoveAssignment(assignment);

            Assert.AreEqual(0, environment.Assignments.Count());
        }

        [Test]
        public void RemoveAssignment_AssignmentPreviouslyAdded_ShouldSucceed()
        {
            var server = StubsBuilder.BuildServerStub();
            var serverRole = StubsBuilder.BuildServerRoleStub();
            var assignment = StubsBuilder.BuildServerAssignmentStub(environment, server, serverRole);
            environment.AddAssignment(assignment);

            environment.RemoveAssignment(assignment);

            Assert.AreEqual(0, environment.Assignments.Count());
        }

        [Test, ExpectedException(typeof(InvalidAssignmentException))]
        public void RemoveAssignment_AssignmentForAnotherEnvironment_ShouldThrowException()
        {
            var anotherEnvironment = StubsBuilder.BuildEnvironmentStub();
            var assignment = StubsBuilder.BuildServerAssignmentStub(anotherEnvironment);

            environment.RemoveAssignment(assignment);
        }
        #endregion

        #region RegisterReleaseDeployment
        [Test]
        public void RegisterReleaseDeployment_NoActiveVersions_ShouldAddAllVersions()
        {
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Active);
            var versions = new List<IVersion>();
            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            release.Stub(x => x.Versions).Return(versions);

            environment.RegisterReleaseDeployment(release);

            Assert.AreEqual(versions.Count, environment.ActiveVersions.Count());
            CollectionAssert.AllItemsAreInstancesOfType(environment.ActiveVersions, typeof(ActiveVersion));
            CollectionAssert.AreEquivalent(versions, environment.ActiveVersions.Select(x => x.Version));
        }

        [Test]
        public void RegisterReleaseDeployment_SecondRegistrationWithMoreVersions_ShouldAddNewVersions()
        {
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Active);
            var versions = new List<IVersion>();
            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            release.Stub(x => x.Versions).Return(versions);
            environment.RegisterReleaseDeployment(release);
            var firstRunVersions = environment.ActiveVersions.ToArray();

            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            versions.Add(Development.StubsBuilder.BuildVersionStub(Development.StubsBuilder.BuildComponentStub(), release));
            environment.RegisterReleaseDeployment(release);

            Assert.AreEqual(versions.Count, environment.ActiveVersions.Count());
            CollectionAssert.AllItemsAreInstancesOfType(environment.ActiveVersions, typeof(ActiveVersion));
            CollectionAssert.AreEquivalent(versions, environment.ActiveVersions.Select(x => x.Version));
            CollectionAssert.IsSubsetOf(firstRunVersions, environment.ActiveVersions);
        }

        [Test]
        public void RegisterReleaseDeployment_SecondRegistrationWithNewerVersions_ShouldAddNewVersionsAndRemoveOldOnes()
        {
            var component1 = Development.StubsBuilder.BuildComponentStub();
            var component2 = Development.StubsBuilder.BuildComponentStub();
            var component3 = Development.StubsBuilder.BuildComponentStub();
            var component4 = Development.StubsBuilder.BuildComponentStub();
            var release1 = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Active);
            var release2 = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Active);
            var component1Version1 = Development.StubsBuilder.BuildVersionStub(component1, release1);
            var component2Version1 = Development.StubsBuilder.BuildVersionStub(component2, release1);
            var component3Version1 = Development.StubsBuilder.BuildVersionStub(component3, release1);
            var component1Version2 = Development.StubsBuilder.BuildVersionStub(component1, release2);
            var component2Version2 = Development.StubsBuilder.BuildVersionStub(component2, release2);
            var component4Version2 = Development.StubsBuilder.BuildVersionStub(component4, release2);

            var versionsRelease1 = new List<IVersion>();
            release1.Stub(x => x.Versions).Return(versionsRelease1);
            versionsRelease1.Add(component1Version1);
            versionsRelease1.Add(component2Version1);
            versionsRelease1.Add(component3Version1);

            var versionsRelease2 = new List<IVersion>();
            release2.Stub(x => x.Versions).Return(versionsRelease2);
            versionsRelease2.Add(component1Version2);
            versionsRelease2.Add(component2Version2);
            versionsRelease2.Add(component4Version2);

            environment.RegisterReleaseDeployment(release1);
            var firstRunVersions = environment.ActiveVersions.ToArray();
            
            environment.RegisterReleaseDeployment(release2);

            Assert.AreEqual(4, environment.ActiveVersions.Count());
            CollectionAssert.AllItemsAreInstancesOfType(environment.ActiveVersions, typeof(ActiveVersion));
            CollectionAssert.IsNotSubsetOf(firstRunVersions, environment.ActiveVersions);
            Assert.AreEqual(0, environment.ActiveVersions.Count(x => x.Version.Equals(component1Version1)));
            Assert.AreEqual(0, environment.ActiveVersions.Count(x => x.Version.Equals(component2Version1)));
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Equals(component3Version1)));
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Equals(component1Version2)));
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Equals(component2Version2)));
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Equals(component4Version2)));
        }

        [Test]
        public void RegisterReleaseDeployment_ReleaseInActiveStatus_ShouldSucceed()
        {
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Active);
            release.Stub(x => x.Versions).Return(new IVersion[0]);

            environment.RegisterReleaseDeployment(release);
        }

        [Test, ExpectedException(typeof(InvalidActionForReleaseStatusException))]
        public void RegisterReleaseDeployment_ReleaseInPlannedStatus_ShouldThrowException()
        {
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Planned);

            environment.RegisterReleaseDeployment(release);
        }

        [Test, ExpectedException(typeof(InvalidActionForReleaseStatusException))]
        public void RegisterReleaseDeployment_ReleaseInReleasedStatus_ShouldThrowException()
        {
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Released);

            environment.RegisterReleaseDeployment(release);
        }

        [Test, ExpectedException(typeof(InvalidActionForReleaseStatusException))]
        public void RegisterReleaseDeployment_ReleaseInObsoleteStatus_ShouldThrowException()
        {
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Obsolete);

            environment.RegisterReleaseDeployment(release);
        }
        #endregion

        #region UnregisterComponentDeployed
        [Test]
        public void UnregisterComponentDeployed_ComponentPreviouslyDeployed_ShouldSucceed()
        {
            var component1 = Development.StubsBuilder.BuildComponentStub();
            var component2 = Development.StubsBuilder.BuildComponentStub();
            var component3 = Development.StubsBuilder.BuildComponentStub();
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Active);
            var component1Version = Development.StubsBuilder.BuildVersionStub(component1, release);
            var component2Version = Development.StubsBuilder.BuildVersionStub(component2, release);
            var component3Version = Development.StubsBuilder.BuildVersionStub(component3, release);
            var versionsRelease = new List<IVersion>();
            release.Stub(x => x.Versions).Return(versionsRelease);
            versionsRelease.Add(component1Version);
            versionsRelease.Add(component2Version);
            versionsRelease.Add(component3Version);
            environment.RegisterReleaseDeployment(release);

            environment.UnregisterComponentDeployed(component2);

            Assert.AreEqual(2, environment.ActiveVersions.Count());
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Component.Equals(component1)));
            Assert.AreEqual(0, environment.ActiveVersions.Count(x => x.Version.Component.Equals(component2)));
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Component.Equals(component3)));
        }

        [Test]
        public void UnregisterComponentDeployed_ComponentNotDeployed_NothingShouldHappen()
        {
            var component1 = Development.StubsBuilder.BuildComponentStub();
            var component2 = Development.StubsBuilder.BuildComponentStub();
            var component3 = Development.StubsBuilder.BuildComponentStub();
            var release = Development.StubsBuilder.BuildReleaseStub(ReleaseStatus.Active);
            var component1Version = Development.StubsBuilder.BuildVersionStub(component1, release);
            var component3Version = Development.StubsBuilder.BuildVersionStub(component3, release);
            var versionsRelease = new List<IVersion>();
            release.Stub(x => x.Versions).Return(versionsRelease);
            versionsRelease.Add(component1Version);
            versionsRelease.Add(component3Version);
            environment.RegisterReleaseDeployment(release);

            environment.UnregisterComponentDeployed(component2);

            Assert.AreEqual(2, environment.ActiveVersions.Count());
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Component.Equals(component1)));
            Assert.AreEqual(0, environment.ActiveVersions.Count(x => x.Version.Component.Equals(component2)));
            Assert.AreEqual(1, environment.ActiveVersions.Count(x => x.Version.Component.Equals(component3)));
        }

        #endregion
    }
}
